﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.IO;

using Factonomy.AzureUtility.Core.Storage;
using Factonomy.AzureUtility.Core.Storage.StorageHttpConstants;

namespace Factonomy.AzureUtility.Core.StorageHelpers
{
    class BlobHelper
    {
        // Events
        //public event NoParameterDelegate LoadAddBlobComplete;
        public event NoParameterDelegate LoadBlobListComplete;
        //public event NoParameterDelegate TaskComplete;
        public event BlobContainerDelegate ContainerDeleted;
        public event NoParameterDelegate BlobListChanged;
        //public event NoParameterDelegate BlobChanged;
        //public event NoParameterDelegate BlobAdded;
        public event NoParameterDelegate BlobDeleted;
        //public event NoParameterDelegate AllBlobsDeletedStart;
        //public event NoParameterDelegate AllBlobsDeletedFinished;
        public event StringParameterDelegate ShowStatus;
        public event StringParameterDelegate StartTask;
        public event NoParameterDelegate EndTask;
        public event EventHandler Error;

        protected virtual void OnShowStatus(string message)
        {
            if (ShowStatus != null)
                ShowStatus(message);
        }

        protected virtual void OnStartTask(string message)
        {
            if (StartTask != null)
                StartTask(message);
        }

        protected virtual void OnEndTask()
        {
            if (EndTask != null)
                EndTask();
        }

        //protected virtual void OnLoadAddBlobComplete()
        //{
        //    if (LoadAddBlobComplete != null)
        //        LoadAddBlobComplete();
        //}


        //protected virtual void OnBlobAdded()
        //{
        //    if (BlobAdded != null)
        //        BlobAdded();
        //}

        protected virtual void OnBlobListChanged()
        {
            if (BlobListChanged != null)
                BlobListChanged();
        }

        protected virtual void OnBlobDeleted()
        {
            if (BlobDeleted != null)
                BlobDeleted();
        }

        //protected virtual void OnAllBlobsDeletedStart()
        //{
        //    if (AllBlobsDeletedStart != null)
        //        AllBlobsDeletedStart();
        //}

        //protected virtual void OnAllBlobsDeleteFinished()
        //{
        //    if (AllBlobsDeletedFinished != null)
        //        AllBlobsDeletedFinished();
        //}

        public virtual void OnLoadBlobListComplete()
        {
            if (LoadBlobListComplete != null)
                LoadBlobListComplete();
        }

        //protected virtual void OnTaskComplete()
        //{
        //    if (TaskComplete != null)
        //        TaskComplete();
        //}

        //public virtual void OnBlobChanged()
        //{
        //    if (BlobChanged != null)
        //        BlobChanged();
        //}

        protected virtual void OnContainerDeleted(BlobContainer container)
        {
            if (ContainerDeleted != null)
                ContainerDeleted(container);
        }

        protected virtual void OnError(Exception ex)
        {
            if (Error != null)
                Error(ex, EventArgs.Empty);
        }

        // Private variables
        private Uri _liveBaseUri =new Uri("http://blob.core.windows.net");
        private Uri _devBaseUri = new Uri("http://127.0.0.1:10000");
        private static BlobHelper _instance;

        public static BlobHelper Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new BlobHelper();
                }
                return _instance;
            }
        }

        public Uri LiveBaseUri
        {
            get { return this._liveBaseUri; }
        }

        public Uri DevBaseUri
        {
            get { return this._devBaseUri; }
        }

        public Uri GetBaseUri(ConfigAccount account)
        {
            BlobStorage storage = this.GetBlobStorage(account);
            return storage.BaseUri;
        }

        private BlobStorage GetBlobStorage(ConfigAccount account)
        {
            BlobStorage storage = BlobStorage.Create(new StorageAccountInfo(account.Local ? this._devBaseUri : this._liveBaseUri, account.Local, account.AccountName, account.SharedKey));
            storage.Timeout = new TimeSpan(0, 1, 0);
            storage.RetryPolicy = RetryPolicies.RetryN(2, TimeSpan.FromMilliseconds(100));
            return storage;
        }

        public bool Test(ConfigAccount account)
        {
            bool result = false;
            {
                try
                {
                    
                    BlobStorage storage = this.GetBlobStorage(account);
                    ResourceUriComponents uriComponents;
                    NameValueCollection param = new NameValueCollection();
                    param.Add("comp", "list");

                    Uri uri = Utilities.CreateRequestUri(storage.BaseUri, account.Local, storage.AccountName, null, null, new TimeSpan(0, 0, 10), param, out uriComponents);
                    HttpWebRequest request = Utilities.CreateHttpRequest(uri, HttpMethod.Get, new TimeSpan(0, 0, 10));
                    SharedKeyCredentials credentials = new SharedKeyCredentials(storage.AccountName, Convert.FromBase64String(storage.Base64Key));
                    credentials.SignRequest(request, uriComponents);

                    try
                    {
                        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                        {
                            if (response.StatusCode == HttpStatusCode.OK)
                                result = true;
                            else if (response.StatusCode == HttpStatusCode.Gone || response.StatusCode == HttpStatusCode.NotFound)
                                result = false;
                            else
                            {
                                Utilities.ProcessUnexpectedStatusCode(response);
                                result = false;
                            }

                            response.Close();
                        }
                    }
                    catch (WebException we)
                    {
                        if (we.Response != null &&
                            ((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Gone ||
                            (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.NotFound)
                            )
                            result = false;
                        else
                            throw Utilities.TranslateWebException(we);
                    }
                }
                catch (Exception ex)
                {
                    result = false;
                    this.OnError(new Exception("An error occured.", ex));
                }
            }
            return result;
        }

        public IEnumerable<BlobContainer> ListContainers(ConfigAccount account)
        {
            try
            {
                BlobStorage storage = this.GetBlobStorage(account);
                return storage.ListBlobContainers();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
            }
            return null;
        }

        public BlobContainer GetContainer(ConfigAccount account, string container)
        {
            try
            {
                BlobStorage storage = this.GetBlobStorage(account);
                return storage.GetBlobContainer(container);
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
            }
            return null;
        }

        public IEnumerable<object> ListBlobs(ConfigAccount account, string containerName)
        {
            try
            {
                BlobStorage storage = this.GetBlobStorage(account);
                if (containerName != null && containerName != string.Empty)
                {
                    BlobContainer container = storage.GetBlobContainer(containerName);
                    return container.ListBlobs("", false);
                }
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
            }

            return null;
        }

        public MemoryStream GetBlobContents(ConfigAccount account, string containerName, string blobName)
        {
            try
            {
                this.OnStartTask("Downloading blob contents...");
                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);
                MemoryStream stream = new MemoryStream();
                BlobContents contents = new BlobContents(stream);
                container.GetBlob(blobName, contents, false);
                stream.Seek(0, SeekOrigin.Begin);
                this.OnEndTask();
                return stream;
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex)); 
            }

            return null;
        }

        public void AddContainer(ConfigAccount account, string containerName)
        {
            try
            {
                this.OnStartTask("Adding storage container...");
                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);
                NameValueCollection containerMetadata = new NameValueCollection();
                containerMetadata.Add("Name", containerName);
                container.CreateContainer(containerMetadata, ContainerAccessControl.Public);
                this.OnEndTask();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex)); 
            }
        }

        public bool DeleteContainer(ConfigAccount account, string containerName)
        {
            try
            {
                this.OnStartTask("Deleting storage container...");

                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);

                foreach (object prop in container.ListBlobs("", false))
                {
                    try
                    {
                        if (prop.GetType() == typeof(BlobProperties))
                        {
                            BlobProperties prop1 = (BlobProperties)prop;

                            this.OnShowStatus("Deleting blob '" + prop1.Name + "'");

                            container.DeleteBlob(prop1.Name);
                        }

                        if (prop.GetType() == typeof(string))
                        {
                            this.OnShowStatus("Deleting blob '" + prop.ToString() + "'");

                            container.DeleteBlob(prop.ToString());
                        }

                    }
                    catch (Exception ex)
                    {
                        this.OnError(new Exception("An error occured.", ex));
                    }
                }

                if (!container.DeleteContainer())
                {
                    return false;
                }
                else
                {
                    this.OnContainerDeleted(container);
                    return true;
                }
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
                return false;
            }
        }

        public void DeleteBlob(ConfigAccount account, string containerName, string blobName)
        {
            try
            {
                this.OnStartTask("Deleting blob...");
                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);

                this.OnShowStatus("Deleting blob '" + blobName + "'");

                container.DeleteBlob(blobName);
                this.OnBlobListChanged();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex)); 
            }
        }

        public void DeleteAllBlobs(ConfigAccount account, string containerName)
        {
            try
            {
                this.OnStartTask("Deleting blobs...");

                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);
                foreach (object prop in container.ListBlobs("", false))
                {

                    if (prop.GetType() == typeof(BlobProperties))
                    {
                        BlobProperties prop1 = (BlobProperties)prop;

                        this.OnShowStatus("Deleting blob '" + prop1.Name + "'");

                        container.DeleteBlob(prop1.Name);
                    }

                    if (prop.GetType() == typeof(string))
                    {
                        this.OnShowStatus("Deleting blob '" + prop.ToString() + "'");

                        container.DeleteBlob(prop.ToString());
                    }
                }

                this.OnBlobListChanged();

            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex)); 
            }

        }

        public void AddFile(ConfigAccount account, string containerName, string path, string root)
        {
            BlobStorage storage = this.GetBlobStorage(account);
            BlobContainer container = storage.GetBlobContainer(containerName);

            try
            {
                using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read))
                {
                    BlobContents contents = new BlobContents(stream);
                    BlobProperties props = new BlobProperties(path.ToLower().Replace(root.ToLower(), ""));
                    props.ContentType = Globals.GetHttpContentType(Path.GetExtension(path));

                    this.OnShowStatus("Uploading '" + path.ToLower() + "'");

                    container.CreateBlob(props, contents, true);
                }

                this.OnBlobListChanged();
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex)); 
            }
        }

        public void AddDirectory(ConfigAccount account, string containerName, string path, string root, bool fireCompleteEvent)
        {
            try
            {
                this.OnStartTask("Uploading blobs...");

                BlobStorage storage = this.GetBlobStorage(account);
                if (!path.Contains(".svn"))
                {
                    BlobContainer container = storage.GetBlobContainer(containerName);

                    DirectoryInfo directory = new DirectoryInfo(path);
                    if (directory.GetFiles().Length > 0)
                    {
                        try
                        {
                            BlobContents contents = new BlobContents(new MemoryStream());
                            BlobProperties props = new BlobProperties(path.ToLower().Replace(root.ToLower(), ""));

                            this.OnShowStatus("Uploading '" + path.ToLower() + "' to blob '" + props.Name + "'");

                            container.CreateBlob(props, contents, true);
                        }
                        catch (Exception ex)
                        {
                            this.OnError(new Exception("An error occured.", ex));
                        }
                    }

                    foreach (FileInfo file in directory.GetFiles())
                    {
                        using (FileStream stream = File.Open(file.FullName, FileMode.Open, FileAccess.Read))
                        {
                            try
                            {
                                BlobContents contents = new BlobContents(stream);
                                BlobProperties props = new BlobProperties(file.FullName.ToLower().Replace(root.ToLower(), ""));
                                props.ContentType = Globals.GetHttpContentType(Path.GetExtension(file.FullName));

                                this.OnShowStatus("Uploading '" + file.FullName.ToLower() + "'");

                                container.CreateBlob(props, contents, true);
                            }
                            catch (Exception ex)
                            {
                                this.OnError(new Exception("An error occured.", ex));
                            }
                        }
                    }

                    foreach (DirectoryInfo subDir in directory.GetDirectories())
                    {
                        this.AddDirectory(account, containerName, subDir.FullName, root, false);
                    }

                    if (fireCompleteEvent)
                    {
                        this.OnBlobListChanged();
                    }
                }
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
            }
        }

        public void SaveBlob(ConfigAccount account, string containerName, string blobName, string contentText)
        {
            try
            {
                BlobStorage storage = this.GetBlobStorage(account);
                BlobContainer container = storage.GetBlobContainer(containerName);

                MemoryStream stream = new MemoryStream();
                byte[] data = Encoding.UTF8.GetBytes(contentText);
                stream.Write(data, 0, data.Length);

                stream.Seek(0, SeekOrigin.Begin);

                BlobContents contents = new BlobContents(stream);
                BlobProperties props = new BlobProperties(blobName);
                props.ContentType = Globals.GetHttpContentType(Path.GetExtension(blobName));
                container.CreateBlob(props, contents, true);
            }
            catch (Exception ex)
            {
                this.OnError(new Exception("An error occured.", ex));
            }
        }
    }
}
